﻿#include "RendererGLES.h"
#include "GraphicsDriverGLES.h"
#include "GeometryDataMgrGLES.h"
#include "VertexBufferGLES.h"
#include "IndexBufferGLES.h"
#include "RenderQueue.h"
#include "ShaderManagerGLES.h"
#include "MainFrameBufferGLES.h"
#include "TextureGLES.h"
#include "FrameBufferObject.h"

namespace Nezha
{

	RendererGLES::RendererGLES(const _string& name)
		:Renderer(name)
		,mAdaptor(NULL)
		,mShaderCompilerSupport(true)
		,mMaxVertexAttribs(0)
		,mDepthFormatSupported(0)
		,mFloatingTextureBits(0)
	{
		mDriver = NULL;
		mGLColMasks[0] = mGLColMasks[1] = mGLColMasks[2] = mGLColMasks[3] = GL_TRUE;
	}


	RendererGLES::~RendererGLES()
	{
		SetGlobal(NULL);
	}

	bool RendererGLES::_initialize(GLESAdaptor* ada)
	{
		mAdaptor = ada;
		SetGlobal(static_cast<Renderer*>(this));

		initializeOnce(ada->_getEngineConfig()->mColorBufferBits, ada->_getEngineConfig()->mScreenWidth,
			ada->_getEngineConfig()->mScreenHeight);

		// TODO
	
		return true;
	}

	void RendererGLES::createManagerImpls()
	{
		createDriverInfo();

		mFrameBuffer = static_cast<RenderTarget*>(NZ_New MainFrameBufferGLES(mColorBit, mWidth, mHeight));
		mGeometryDataManager = static_cast<GeometryDataManager*>(NZ_New GeometryDataMgrGLES(this));
		mShaderProgramManager = static_cast<ShaderProgramManager*>(NZ_New ShaderManagerGLES());
		mTextureManager = static_cast<TextureManager*>(NZ_New TextureManagerGLES(this));
		mRTTManager = static_cast<RTTManager*>(NZ_New FBORTTManager());
	}

	void RendererGLES::destroyManagerImpls()
	{
		NEZHA_DELETE(mRTTManager);
		NEZHA_DELETE(mTextureManager)
		NEZHA_DELETE(mShaderProgramManager)
		NEZHA_DELETE(mGeometryDataManager)
		NEZHA_DELETE(mFrameBuffer)
		NEZHA_DELETE(mDriver)
	}
	
	void RendererGLES::_update(float dt)
	{
		// TODO
	}

	void RendererGLES::createDriverInfo()
	{
		GraphicsDriverGLES* driver = NZ_New GraphicsDriverGLES();
		mDriver = static_cast<GraphicsDriver*>(driver);
		GraphicsDriver::InfoStruct& driverInfo = driver->_getInfoStructDirectly();

		const GLubyte* cbVersion = glGetString(GL_VERSION);
		driverInfo.mVersion.fromString(_string((const char*)cbVersion));
		const GLubyte* cbVendor = glGetString(GL_VENDOR);
		driverInfo.mVendorName = (const char*)cbVendor;
		const GLubyte* cbRenderer = glGetString(GL_RENDERER);
		driverInfo.mDeviceName = (const char*)cbRenderer;

		GLint glint;
		glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &glint);
		driverInfo.mNumTexureUnits = glint;
		glGetIntegerv(GL_STENCIL_BITS, &glint);
		driverInfo.mStencilBufferBits = glint;

		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_STENCIL_WRAP);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_STENCIL_SEPARATE);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_SCISSOR);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_HARDWARE_RT);
		driverInfo.mNumRenderTargetsSupport = 1;

		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_AUTOMIPMAP);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_CUBEMAPPING);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_POINT_SPRITES);
		GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_INFINITE_FAR_PLANE);

		GLboolean glbool = GL_TRUE;
		glGetBooleanv(GL_SHADER_COMPILER, &glbool);
		mShaderCompilerSupport = (glbool == GL_TRUE);
		// TODO warnning if can't compile shader at run-time.

		GLfloat glfloat = 0;
		glGetFloatv(GL_MAX_VERTEX_UNIFORM_VECTORS, &glfloat);
		driverInfo.mNumVSConstFloat = (u16)glfloat;
		driverInfo.mNumVSConstInt = (u16)glfloat;
		driverInfo.mNumVSConstBool = (u16)glfloat;

		glGetFloatv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &glfloat);
		driverInfo.mNumPSConstFloat = (u16)glfloat;
		driverInfo.mNumPSConstInt = (u16)glfloat;
		driverInfo.mNumPSConstBool = (u16)glfloat;

		// TODO check hardware occlusion & other extensions

		// space-sperated string description of extensions.
		const char* extensionStr = (const char*)glGetString(GL_EXTENSIONS);
		_stringStream strStreams;
		StringArray extensionArr;

		strStreams << extensionStr;

		_string str;
		while(strStreams >> str)
		{
			extensionArr.push_back(str);
		}

		StringArray::iterator it = find(extensionArr.begin(), extensionArr.end(), "GL_EXT_occlusion_query_boolean");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlag, GC_HARDWARE_OCCLUSION);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_compressed_ETC1_RGB8_texture");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlagGL, GC_TEXTURE_ETC);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_IMG_texture_compression_pvrtc");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlagGL, GC_TEXTURE_PVR);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_EXT_texture_compression_dxt1");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlagGL, GC_TEXTURE_DXT);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_EXT_texture_filter_anisotropic");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlagGL, GC_ANISOTROPY);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_get_program_binary");
		if(it != extensionArr.end())
		{
			GraphicsDriver::SetCapsFlag(driverInfo.mCapsFlagGL, GC_GET_COMPILED_SHADER_BUFFER);
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_packed_depth_stencil");
		if(it != extensionArr.end())
		{
			mDepthFormatSupported |= DEPTH_PACKED_SEPTH_STENCIL;
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_depth24");
		if(it != extensionArr.end())
		{
			mDepthFormatSupported |= DEPTH_24BIT;
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_depth32");
		if(it != extensionArr.end())
		{
			mDepthFormatSupported |= DEPTH_32BIT;
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_texture_half_float");
		if(it != extensionArr.end())
		{
			mFloatingTextureBits = 16;
		}

		it = find(extensionArr.begin(), extensionArr.end(), "GL_OES_texture_float");
		if(it != extensionArr.end())
		{
			mFloatingTextureBits = 32;
		}

		mDriver->printLog();

		// TODO print extra caps for opengles2 caps.

		glint = 0;
		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &glint);
		mMaxVertexAttribs = (u16)glint;
	}

	void RendererGLES::_uninitialize()
	{
		// TODO 清理一些私有资源

		Renderer::uninitialize();
	}

	bool RendererGLES::beginFrame()
	{
		if(!Renderer::beginFrame())
		{
			return false;
		}

		glEnable(GL_SCISSOR_TEST);

		return true;
	}

	void RendererGLES::endFrame()
	{
		glDisable(GL_SCISSOR_TEST);

		Renderer::endFrame();
	}

	void RendererGLES::clearFameBuffer(u8 clearFlag, const RGBA& col /* = RGBA::BLACK */, float depth /* = 1.0f */, u16 stencil /* = 0 */)
	{
		GLbitfield _clearFlag = 0;

		bool colorInject = mGLColMasks[0] == GL_FALSE || mGLColMasks[1] == GL_FALSE
			|| mGLColMasks[2] == GL_FALSE || mGLColMasks[3] == GL_FALSE;

		if(clearFlag & FBO_COLOR)
		{
			_clearFlag |= GL_COLOR_BUFFER_BIT;

			if(colorInject)
			{
				// restore to write color
				glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
			}

			glClearColor(col.r, col.g, col.b, col.a);
		}

		if(clearFlag & FBO_DEPTH)
		{
			_clearFlag |= GL_DEPTH_BUFFER_BIT;

			if(!getCurDepth().mWritable)
			{
				glDepthMask(GL_TRUE);
			}

			glClearDepthf(depth);
		}

		if(clearFlag & FBO_STENCIL)
		{
			_clearFlag |= GL_STENCIL_BUFFER_BIT;
			glStencilMask(0xFFFFFFFF);
			glClearStencil(stencil);
		}

		GLboolean scissorTest = glIsEnabled(GL_SCISSOR_TEST);

		if(!scissorTest)
		{
			glEnable(GL_SCISSOR_TEST);
		}

		bool needSetScissor = false;

		GLint vp[4], sc[4];
		glGetIntegerv(GL_VIEWPORT, vp);
		glGetIntegerv(GL_SCISSOR_BOX, sc);

		needSetScissor = vp[0] != sc[0] || vp[1] != sc[1] || vp[2] != sc[2] || vp[3] != sc[3];

		if(needSetScissor)
		{
			glScissor(vp[0], vp[1], vp[2], vp[3]);
		}

		glClear(_clearFlag);

		if(needSetScissor)
		{
			glScissor(sc[0], sc[1], sc[2], sc[3]);
		}

		if(!scissorTest)
		{
			glDisable(GL_SCISSOR_TEST);
		}

		if(!getCurDepth().mWritable && (clearFlag & FBO_DEPTH))
		{
			glDepthMask(GL_FALSE);
		}

		if(colorInject && (clearFlag & FBO_COLOR))
		{
			glColorMask(mGLColMasks[0], mGLColMasks[1], mGLColMasks[2], mGLColMasks[3]);
		}

		if(clearFlag & FBO_STENCIL)
		{
			glStencilMask(getCurStencil().mMask);
		}
	}

	void RendererGLES::setRenderTarget(RenderTarget* rt)
	{
		if(rt)
		{
			if(rt != mCurrentRenderTarget)
			{
				if(rt == mFrameBuffer)
				{
					// bind to main
#ifdef NZOS_IOS

					glBindFramebuffer(GL_FRAMEBUFFER, 1);

#else

					glBindFramebuffer(GL_FRAMEBUFFER, 0);

#endif //end NZOS_IOS
				}
				else
				{
					mRTTManager->setCurrent(rt);
				}
			}
		}
		else
		{
			// bind to main
#ifdef NZOS_IOS

			glBindFramebuffer(GL_FRAMEBUFFER, 1);

#else

			glBindFramebuffer(GL_FRAMEBUFFER, 0);

#endif //end NZOS_IOS
		}

		Renderer::setRenderTarget(rt);
	}

	void RendererGLES::setViewport(Viewport *vp)
	{
		if(vp)
		{
			if(vp != mCurrentViewport)
			{
				GLsizei x, y, w, h;

				x = vp->getRect().LeftCorner.x;
				y = vp->getRect().LeftCorner.y;
				w = vp->getRect().getWidth();
				h = vp->getRect().getHeight();

				glViewport(x, y, w, h);

				glScissor(x, y, w, h);
			}
		}

		Renderer::setViewport(vp);
	}

	void RendererGLES::setSamplerImpl(u8 index, u8 totalNum, RenderPass* pass)
	{
		GLenum texIndex = static_cast<GLenum>(GL_TEXTURE0 + index);
		glActiveTexture(texIndex);
		ShaderSampler* ss = pass->getSamper(index);
		TexturePtr texPtr = mTextureManager->getTexture(ss->getSamplerInfo().mTextureID);

		if(!texPtr)
		{
			NZError("Current sampler has no texture assigned.");
			return;
		}
		
		if(texPtr->getType() == TT_2D)
		{
			TextureGLES* tex_gles = static_cast<TextureGLES*>(texPtr.get());
			glBindTexture(GL_TEXTURE_2D, tex_gles->getGLHandle());
			GL_ERROR_DUMP(mAdaptor);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GLenumMapping::GLTexWrap((ShaderSampler::WrapMode)ss->getSamplerInfo().mWrapU));
			GL_ERROR_DUMP(mAdaptor);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GLenumMapping::GLTexWrap((ShaderSampler::WrapMode)ss->getSamplerInfo().mWrapV));
			GL_ERROR_DUMP(mAdaptor);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GLenumMapping::GLTexFilter((ShaderSampler::Filter)ss->getSamplerInfo().mMinFilter));
			GL_ERROR_DUMP(mAdaptor);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GLenumMapping::GLTexFilter((ShaderSampler::Filter)ss->getSamplerInfo().mMagFilter));
			GL_ERROR_DUMP(mAdaptor);
		}
		// TODO other types
	}

	void RendererGLES::setShaderImpl(ShaderProgram* sp)
	{
		if(!sp->isValid() || sp->getLanguageType() != ShaderProgram::GLSL)
		{
			NZError("Invalid shader %s", sp->getName().c_str());

			// fallback to default shader
		}

		ProgramGLES* pg_gles = static_cast<ProgramGLES*>(sp);

		if(!pg_gles->bindToCurrent())
		{
			// FIXME if failed to bind current shader, we wont render nothing.
		}
	}

	void RendererGLES::bindGLBuffer(GLenum target, GLint bufferID)
	{
		BindedBufferMap::iterator it = mBindedBuffers.find(target);

		if(it != mBindedBuffers.end())
		{
			if(it->second != bufferID)
			{
				glBindBuffer(target, bufferID);
				GL_ERROR_DUMP(mAdaptor);

				it->second = bufferID;
			}
		}
		else
		{
			glBindBuffer(target, bufferID);
			GL_ERROR_DUMP(mAdaptor);

			mBindedBuffers.insert(MapPair<GLenum, GLint>(target, bufferID));
		}
	}

	void RendererGLES::unbindGLBuffer(GLenum target, GLint bufferID)
	{
		BindedBufferMap::iterator it = mBindedBuffers.find(target);

		if(it != mBindedBuffers.end() && it->second == bufferID)
		{
			mBindedBuffers.erase(it);
		}
	}

	void RendererGLES::_setBlending(const RS_Blending& as, const RS_Blending* lastAs /* = NULL */)
	{
		// for opengl es2, no directly alpha test fixed-function.

		if(lastAs)
		{
			if(as.mBlendingEnable)
			{
				if(!lastAs->mBlendingEnable)
				{
					glEnable(GL_BLEND);
				}

				bool needSetBlendFunc = false;

				if(!lastAs->mBlendingEnable || lastAs->mSrc != as.mSrc || lastAs->mDst != as.mDst || lastAs->mSeparateBlending !=
					as.mSeparateBlending)
					needSetBlendFunc = true;

				if(!needSetBlendFunc && lastAs->mSeparateBlending && as.mSeparateBlending)
				{
					needSetBlendFunc = as.mSrcAlpha != lastAs->mSrcAlpha || as.mDstAlpha != lastAs->mDstAlpha;
				}

				if(needSetBlendFunc)
				{
					if(as.mSeparateBlending)
						glBlendFuncSeparate(GLenumMapping::GLBlendFactor(as.mSrc), GLenumMapping::GLBlendFactor(as.mDst),
						GLenumMapping::GLBlendFactor(as.mSrcAlpha), GLenumMapping::GLBlendFactor(as.mDstAlpha));
					else
						glBlendFunc(GLenumMapping::GLBlendFactor(as.mSrc), GLenumMapping::GLBlendFactor(as.mDst));
				}

				bool needSetBlendEqual = false;

				if(!lastAs->mBlendingEnable || as.mOp != lastAs->mOp)
					needSetBlendEqual = true;

				if(!needSetBlendEqual && as.mSeparateBlending && !lastAs->mSeparateBlending)
					needSetBlendEqual = true;

				if(!needSetBlendEqual && lastAs->mSeparateBlending
						&& as.mAlphaOp != lastAs->mAlphaOp)
					needSetBlendEqual = true;
				
				if(needSetBlendEqual)
				{
					GLint func = GLenumMapping::GLBlendOp(as.mOp);

					if(as.mSeparateBlending)
					{
						glBlendEquationSeparate(func, GLenumMapping::GLBlendOp(as.mAlphaOp));
					}
					else
					{
						glBlendEquation(func);
					}
				}
			}
			else
			{
				if(lastAs->mBlendingEnable)
				{
					glDisable(GL_BLEND);
				}
			}
		}
		else
		{
			if(as.mBlendingEnable)
			{
				glEnable(GL_BLEND);

				if(as.mSeparateBlending)
					glBlendFuncSeparate(GLenumMapping::GLBlendFactor(as.mSrc), GLenumMapping::GLBlendFactor(as.mDst),
					GLenumMapping::GLBlendFactor(as.mSrcAlpha), GLenumMapping::GLBlendFactor(as.mDstAlpha));
				else
					glBlendFunc(GLenumMapping::GLBlendFactor(as.mSrc), GLenumMapping::GLBlendFactor(as.mDst));

				GLint func = GLenumMapping::GLBlendOp(as.mOp);

				if(as.mSeparateBlending)
					glBlendEquationSeparate(func, GLenumMapping::GLBlendOp(as.mAlphaOp));
				else
					glBlendEquation(func);
			}
			else
			{
				glDisable(GL_BLEND);
			}
		}

		// FIXME alphaToCoverage ?? Opengl es2 support no pipeline's alpha test.
	}

	void RendererGLES::_setCullOrder(const RS_CullOrder& co, const RS_CullOrder* lastCull /* = NULL */)
	{
		if(lastCull && *lastCull == co)
		{
			return;
		}

		if(co == RSCO_NONE)
		{
			glDisable(GL_CULL_FACE);
			GL_ERROR_DUMP(mAdaptor);
			return;
		}

		// FIXME determine if texture flipping or invert vertex winding.
		if(co == RSCO_CCW)
		{
			glCullFace(GL_BACK);
		}
		else
		{
			glCullFace(GL_FRONT);
		}

		GL_ERROR_DUMP(mAdaptor);
	}

	void RendererGLES::_setDepth(const RS_Depth& rd, const RS_Depth* lastDepth /* = NULL */)
	{
		if(lastDepth)
		{
			if(rd.mEnabled)
			{
				if(!lastDepth->mEnabled)
				{
					glEnable(GL_DEPTH_TEST);
				}

				if(!lastDepth->mEnabled || lastDepth->mCmpMode != rd.mCmpMode)
				{
					glDepthFunc(GLenumMapping::GLCompareFunc(rd.mCmpMode));
				}
			}
			else
			{
				if(lastDepth->mEnabled)
				{
					glDisable(GL_DEPTH_TEST);
				}
			}

			if(rd.mWritable != lastDepth->mWritable)
			{
				glDepthMask(rd.mWritable ? GL_TRUE : GL_FALSE);
			}
		}
		else
		{
			if(rd.mEnabled)
			{
				glEnable(GL_DEPTH_TEST);

				glDepthFunc(GLenumMapping::GLCompareFunc(rd.mCmpMode));
			}
			else
			{
				glDisable(GL_DEPTH_TEST);
			}

			if(rd.mWritable)
			{
				glDepthMask(rd.mWritable ? GL_TRUE : GL_FALSE);
			}
		}
	}

	void RendererGLES::_setStencil(const RS_Stencil& st, const RS_Stencil* lastStencil /* = NULL */)
	{
		if(lastStencil)
		{
			if(st.mEnabled)
			{
				if(!lastStencil->mEnabled)
				{
					glEnable(GL_STENCIL_TEST);
				}

				if(st.mTwoSide && mDriver->hasCapability(GC_STENCIL_SEPARATE))
				{
					bool flip = false; // FIXME determine if texture flipping or invert vertex winding.

					if(!lastStencil->mEnabled || st.mWriteMask != lastStencil->mWriteMask)
					{
						glStencilMaskSeparate(GL_BACK, st.mWriteMask);
						glStencilMaskSeparate(GL_FRONT, st.mWriteMask);
					}

					if(!lastStencil->mEnabled || st.mCmpMode != lastStencil->mCmpMode
						|| st.mMask != lastStencil->mMask)
					{
						glStencilFuncSeparate(GL_BACK, GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);
						glStencilFuncSeparate(GL_FRONT, GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);
					}

					if(!lastStencil->mEnabled || st.mOnFail != lastStencil->mOnFail || st.mOnPass != lastStencil->mOnPass || st.mOnZFail != lastStencil->mOnZFail)
					{
						glStencilOpSeparate(GL_BACK, GLenumMapping::GLStencilOp(st.mOnFail, !flip),
							GLenumMapping::GLStencilOp(st.mOnZFail, !flip),
							GLenumMapping::GLStencilOp(st.mOnPass, !flip));

						glStencilOpSeparate(GL_FRONT, GLenumMapping::GLStencilOp(st.mOnFail, !flip),
							GLenumMapping::GLStencilOp(st.mOnZFail, !flip),
							GLenumMapping::GLStencilOp(st.mOnPass, !flip));
					}
				}
				else
				{
					if(!lastStencil->mEnabled || st.mWriteMask != lastStencil->mWriteMask)
						glStencilMask(st.mWriteMask);

					if(!lastStencil->mEnabled || st.mCmpMode != lastStencil->mCmpMode || st.mMask != lastStencil->mMask)
						glStencilFunc(GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);

					if(!lastStencil->mEnabled || st.mOnFail != lastStencil->mOnFail || st.mOnPass != lastStencil->mOnPass || st.mOnZFail != lastStencil->mOnZFail)
						glStencilOp(GLenumMapping::GLStencilOp(st.mOnFail, false),
							GLenumMapping::GLStencilOp(st.mOnZFail, false),
							GLenumMapping::GLStencilOp(st.mOnPass, false));
				}
			}
			else
			{
				if(lastStencil->mEnabled)
				{
					glDisable(GL_STENCIL_TEST);
				}
			}
		}
		else
		{
			if(st.mEnabled)
			{
				glEnable(GL_STENCIL_TEST);

				if(st.mTwoSide && mDriver->hasCapability(GC_STENCIL_SEPARATE))
				{
					bool flip = false; // FIXME determine if texture flipping or invert vertex winding.

					glStencilMaskSeparate(GL_BACK, st.mWriteMask);
					glStencilMaskSeparate(GL_FRONT, st.mWriteMask);

					glStencilFuncSeparate(GL_BACK, GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);
					glStencilFuncSeparate(GL_FRONT, GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);

					glStencilOpSeparate(GL_BACK, GLenumMapping::GLStencilOp(st.mOnFail, !flip),
						GLenumMapping::GLStencilOp(st.mOnZFail, !flip),
						GLenumMapping::GLStencilOp(st.mOnPass, !flip));

					glStencilOpSeparate(GL_FRONT, GLenumMapping::GLStencilOp(st.mOnFail, !flip),
						GLenumMapping::GLStencilOp(st.mOnZFail, !flip),
						GLenumMapping::GLStencilOp(st.mOnPass, !flip));
				}
				else
				{
					glStencilMask(st.mWriteMask);

					glStencilFunc(GLenumMapping::GLCompareFunc(st.mCmpMode), st.mReference, st.mMask);

					glStencilOp(GLenumMapping::GLStencilOp(st.mOnFail, false),
							GLenumMapping::GLStencilOp(st.mOnZFail, false),
							GLenumMapping::GLStencilOp(st.mOnPass, false));
				}
			}
			else
			{
				glDisable(GL_STENCIL_TEST);
			}
		}

	}

	void RendererGLES::_setColorMask(const RS_ColorMask& cm, const RS_ColorMask* lastCM /* = NULL */)
	{
		mGLColMasks[0] = cm.mRed != 0 ? GL_TRUE : GL_FALSE;
		mGLColMasks[1] = cm.mGreen != 0 ? GL_TRUE : GL_FALSE;
		mGLColMasks[2] = cm.mBlue != 0 ? GL_TRUE : GL_FALSE;
		mGLColMasks[3] = cm.mAlpha != 0 ? GL_TRUE : GL_FALSE;
	}

	void RendererGLES::_setPolygonOffset(const RS_PolygonOffset& po, const RS_PolygonOffset* lastOffset /* = NULL */)
	{
		if(lastOffset)
		{
			if(po.mFillEnabled)
			{
				if(!lastOffset->mFillEnabled)
				{
					glEnable(GL_POLYGON_OFFSET_FILL);
				}

				if(!lastOffset->mFillEnabled || lastOffset->mBias != po.mBias || lastOffset->mScale != po.mScale)
				{
					glPolygonOffset(po.mScale, po.mBias);
				}
			}
			else
			{
				if(lastOffset->mFillEnabled)
				{
					glDisable(GL_POLYGON_OFFSET_FILL);
				}
			}
		}
		else
		{
			if(po.mFillEnabled)
			{
				glEnable(GL_POLYGON_OFFSET_FILL);
				glPolygonOffset(po.mScale, po.mBias);
			}
			else
			{
				glDisable(GL_POLYGON_OFFSET_FILL);
			}
		}
	}

	void RendererGLES::_setFillMode(const RS_FillMode& fm, const RS_FillMode* lastFM /* = NULL */)
	{
		// need do nothing for opengl es2.
	}

	bool RendererGLES::makeInputAttribCompatible(GeometryDataBatch* gd, ShaderProgram* spg)
	{
		ProgramGLES* pg = static_cast<ProgramGLES*>(spg);
		const VertexDeclaration& vd = gd->getData()->getVertexDecl();
		VertexElementArray& vea = pg->_internalAccessPredVEA();
		const VertexElement* vd_arr[NZ_MAX_VERTEX_ATTRS] = {0};
		u32 numVea = vea.size();
		u32 numVd = vd.numElements();

		if(numVd < numVea)
		{
			return false;
		}

		for(u32 i = 0; i < numVd; i++)
		{
			vd_arr[i] = vd.getElement(i);
		}
 
		for(u32 i = 0; i < numVea; i++)
		{
			bool foundMatch = false;
			for(u32 j = 0;j < numVd; j++)
			{
				if(vd_arr[j] && vd_arr[j]->Sementic == vea[i].Sementic)
				{
					if(vea[i].Type == vd_arr[j]->Type)
					{
						foundMatch = true;
						vea[i].StreamIndex = vd_arr[j]->StreamIndex;
						vea[i].Offset = vd_arr[j]->Offset;
						vea[i].Type = vd_arr[j]->Type;
						vea[i].UsageIndex = vd_arr[j]->UsageIndex;
						vd_arr[j] = NULL;
						break;
					}
				}
			}

			if(!foundMatch)
			{
				return false;
			}
		}

		return true;
	}

	void RendererGLES::setGeometryImpl(GeometryDataBatch* gdb, GeometryDataBatch* prevGdb)
	{

	}

	void RendererGLES::setShaderParameterImpl(ShaderParameter* sp, RenderPass* rp)
	{
		ShaderParamDataType spdt = sp->getDataType();

		// FIXME sampler
		// FIXME matrices need transpose for glsl. consider 2x3 4x3 2x4 3x4
		switch(spdt)
		{
		case SPDT_INT:
			glUniform1i((GLint)sp->getHandle(), *sp->getData<int>());
			break;
		case SPDT_INT2:
			glUniform2iv((GLint)sp->getHandle(), 1, (const GLint*)sp->getData<Pointi>());
			break;
		case SPDT_INT3:
			glUniform3iv((GLint)sp->getHandle(), 1, (const GLint*)sp->getData<Vec3i>());
			break;
		case SPDT_INT2X3:
			glUniform3iv((GLint)sp->getHandle(), 2, (const GLint*)sp->getData<Vec3i>());
			break;
		case SPDT_INT3X3:
			glUniform3iv((GLint)sp->getHandle(), 3, (const GLint*)sp->getData<Vec3i>());
			break;
		case SPDT_INT4X3:
			glUniform3iv((GLint)sp->getHandle(), 4, (const GLint*)sp->getData<Vec3i>());
			break;
		case SPDT_INT4:
			glUniform4iv((GLint)sp->getHandle(), 1, (const GLint*)sp->getData<Vec4i>());
			break;
		case SPDT_INT2X4:
			glUniform4iv((GLint)sp->getHandle(), 2, (const GLint*)sp->getData<Vec4i>());
			break;
		case SPDT_INT3X4:
			glUniform4iv((GLint)sp->getHandle(), 3, (const GLint*)sp->getData<Vec4i>());
			break;
		case SPDT_INT4X4:
			glUniform4iv((GLint)sp->getHandle(), 4, (const GLint*)sp->getData<Vec4i>());
			break;
		case SPDT_FLOAT:
			glUniform1f((GLint)sp->getHandle(), *sp->getData<float>());
			break;
		case SPDT_FLOAT2:
			glUniform2fv((GLint)sp->getHandle(), 1, (const GLfloat*)sp->getData<Vector2>());
			break;
		case SPDT_FLOAT3:
			glUniform3fv((GLint)sp->getHandle(), 1, (const GLfloat*)sp->getData<Vector3>());
			break;
		case SPDT_FLOAT4:
			glUniform4fv((GLint)sp->getHandle(), 1, (const GLfloat*)sp->getData<Vector4>());
			break;
		case SPDT_FLOAT2X3:
			glUniform3fv((GLint)sp->getHandle(), 2, (const GLfloat*)sp->getData<Vector3>());
			break;
		case SPDT_FLOAT2X4:
			glUniform4fv((GLint)sp->getHandle(), 2, (const GLfloat*)sp->getData<Vector4>());
			break;
		case SPDT_FLOAT3X3:
			{
				Matrix3x3& m3 = *sp->getData<Matrix3x3>();
				Matrix3x3 tmp = m3.transpose();
				glUniformMatrix3fv((GLint)sp->getHandle(), 1, GL_FALSE, (const GLfloat*)&tmp);
			}
			break;
		case SPDT_FLOAT3X4:
			glUniform4fv((GLint)sp->getHandle(), 3, (const GLfloat*)sp->getData<Vector4>());
			break;
		case SPDT_FLOAT4X3:
			glUniform3fv((GLint)sp->getHandle(), 4, (const GLfloat*)sp->getData<Vector3>());
			break;
		case SPDT_FLOAT4X4:
			{
				Matrix4x4& m4 = *sp->getData<Matrix4x4>();
				Matrix4x4 tmp = m4.transpose();
				glUniformMatrix4fv((GLint)sp->getHandle(), 1, GL_FALSE, (const GLfloat*)&tmp);
			}			
			break;
		}

		GL_ERROR_DUMP(mAdaptor);
	}

	void RendererGLES::drawSingle()
	{
		Renderer::drawSingle();

		const GeometryData* gd = mCurrentBatch.getData();
		const VertexDeclaration& decl = gd->getVertexDecl();
		//u32 numElems = decl.numElements();

		ProgramGLES* pg = static_cast<ProgramGLES*>(mCurrentShader);
		VertexElementArray& vea = pg->_internalAccessPredVEA();
		u32 numElems = vea.size();
	
		if(numElems > mMaxVertexAttribs)
		{
			NZWarning("Object %s has %d vertex attribs exceed the opengles 2 limitation %d", mCurrentRenderQueueEntry->RObject->getROName().c_str(), numElems, mMaxVertexAttribs);
			numElems = mMaxVertexAttribs;
		}

		// in fact, this can be done beofore drawGeometry().
		//if(!vertexElementsCapable(decl, pg))
		//{
		//	NZWarning("Shader require some vertex attribute, but geometry data has not. when rendering object %s", mCurrentRenderQueueEntry->RObject->getROName().c_str());
		//}

		void* ptr = NULL;
		GLint attribLoc = 0;
		PArray<GLint> activedAttribs;

		for(u32 i = 0; i < numElems; i++)
		{
			VertexElement* ve = &vea[i];

			VertexBufferPtr vb = gd->getVertexBuffer(ve->StreamIndex);
			VertexBufferGLES* vbgles = static_cast<VertexBufferGLES*>(vb.get());

			if(!vbgles)
			{
				NZError("Invalid stream <%d> while rendering object %s", ve->StreamIndex, mCurrentRenderQueueEntry->RObject->getROName().c_str());
				continue;
			}

			ptr = VBO_BUFFER_OFFSET(ve->Offset + mCurrentBatch.getNumVertexsOffset() * vbgles->getVertexStride());

			vbgles->submit();
			u8* vbBuf = (u8*)vbgles->_internalAccess_FEOV();

			u16 dataSize = VertexDeclaration::ElementTypeSize(ve->Type);
			u16 dataTypeCount = VertexDeclaration::ElementTypeCount(ve->Type);

			GLboolean normalized = GL_FALSE;

			if(ve->Type == VET_COLOR)
			{
				dataTypeCount = 4;
				normalized = GL_TRUE;
			}

			bindGLBuffer(GL_ARRAY_BUFFER, vbgles->getBufferId());

			attribLoc = pg->getAttribLocation(ve->Sementic);

			glVertexAttribPointer(attribLoc, dataTypeCount, GLenumMapping::VertexAttrib((u32)ve->Type), normalized, static_cast<GLsizei>(vbgles->getVertexStride()), (const void*)ptr);
			GL_ERROR_DUMP(mAdaptor);

			glEnableVertexAttribArray(attribLoc);
			GL_ERROR_DUMP(mAdaptor);

			activedAttribs.push_back(attribLoc);
		}

		IndexBufferPtr ib = gd->getIndexBuffer();

		GLenum primitiveTy = primitiveTy = GLenumMapping::GeometryDataType(gd->getGeometryType());
		if(mGlobalFillMode != RSFM_IGNORE)
		{
			if(mGlobalFillMode != RSFM_SOLID)
				primitiveTy = GLenumMapping::GLFillMode(mGlobalFillMode);
		}
		else
		{
			if(mCurrentRenderQueueEntry->RPass->mFillMode != RSFM_SOLID)
				primitiveTy = GLenumMapping::GLFillMode(mCurrentRenderQueueEntry->RPass->mFillMode);	
		}

		if(ib.notNull())
		{
			IndexBufferGLES* ibgles = static_cast<IndexBufferGLES*>(ib.get());
			ibgles->submit();

			bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER, ibgles->getBufferId());

			ptr = VBO_BUFFER_OFFSET(mCurrentBatch.getNumIndicesOffset() * ibgles->getStride());

			GLenum glIndexType = ibgles->getIndexType() == IndexBuffer::IT_16BIT ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;

			glDrawElements(primitiveTy, ibgles->getNumIndices(), glIndexType, ptr);
			GL_ERROR_DUMP(mAdaptor);

		}
		else if(ptr)
		{
			glDrawArrays(primitiveTy, 0, mCurrentBatch.getNumVerts());
			GL_ERROR_DUMP(mAdaptor);
		}

		for(PArray<GLint>::iterator it = activedAttribs.begin(); it != activedAttribs.end(); it++)
		{
			glDisableVertexAttribArray((*it));
			GL_ERROR_DUMP(mAdaptor);
		}
	}

}//end namespace Nezha